
<!DOCTYPE HTML>
<html lang="zh-hans" >
    <head>
        <meta charset="UTF-8">
        <title>6 模板 · C++导览 第二版 简体中文版</title>
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="description" content="">
        <meta name="generator" content="HonKit 5.1.1">
        <meta name="author" content="Windsting">
        
        
    
    <link rel="stylesheet" href="gitbook/style.css">

    
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-forkmegithub/plugin.css">
                
            
                
                <link rel="stylesheet" href="gitbook/@honkit/honkit-plugin-highlight/website.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-search/search.css">
                
            
                
                <link rel="stylesheet" href="gitbook/gitbook-plugin-fontsettings/website.css">
                
            
        

    

    
        
        <link rel="stylesheet" href="styles/website.css">
        
    
        
    
        
    
        
    
        
    
        
    

        
    
    
    <meta name="HandheldFriendly" content="true"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <link rel="apple-touch-icon-precomposed" sizes="152x152" href="gitbook/images/apple-touch-icon-precomposed-152.png">
    <link rel="shortcut icon" href="gitbook/images/favicon.ico" type="image/x-icon">

    
    <link rel="next" href="ch07.html" />
    
    
    <link rel="prev" href="ch05.html" />
    

    </head>
    <body>
        
<div class="book honkit-cloak">
    <div class="book-summary">
        
            
<div id="book-search-input" role="search">
    <input type="text" placeholder="输入并搜索" />
</div>

            
                <nav role="navigation">
                


<ul class="summary">
    
    
    
        
        <li>
            <a href="https://github.com/windsting/a-tour-of-cpp-2nd-cn" target="_blank" class="custom-link">Github Link</a>
        </li>
    
    

    
    <li class="divider"></li>
    

    
        
        
    
        <li class="chapter " data-level="1.1" data-path="translation_note.html">
            
                <a href="translation_note.html">
            
                    
                    译者言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.2" data-path="./">
            
                <a href="./">
            
                    
                    前言
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.3" data-path="ch01.html">
            
                <a href="ch01.html">
            
                    
                    1 基础知识
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.4" data-path="ch02.html">
            
                <a href="ch02.html">
            
                    
                    2 用户定义类型
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.5" data-path="ch03.html">
            
                <a href="ch03.html">
            
                    
                    3 模块化
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.6" data-path="ch04.html">
            
                <a href="ch04.html">
            
                    
                    4 类
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.7" data-path="ch05.html">
            
                <a href="ch05.html">
            
                    
                    5 基本操作
            
                </a>
            

            
        </li>
    
        <li class="chapter active" data-level="1.8" data-path="ch06.html">
            
                <a href="ch06.html">
            
                    
                    6 模板
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.9" data-path="ch07.html">
            
                <a href="ch07.html">
            
                    
                    7 概束和泛型编程
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.10" data-path="ch08.html">
            
                <a href="ch08.html">
            
                    
                    8 标准库概览
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.11" data-path="ch09.html">
            
                <a href="ch09.html">
            
                    
                    9 字符串和正则表达式
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.12" data-path="ch10.html">
            
                <a href="ch10.html">
            
                    
                    10 输入输出
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.13" data-path="ch11.html">
            
                <a href="ch11.html">
            
                    
                    11 容器
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.14" data-path="ch12.html">
            
                <a href="ch12.html">
            
                    
                    12 算法
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.15" data-path="ch13.html">
            
                <a href="ch13.html">
            
                    
                    13 实用功能
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.16" data-path="ch14.html">
            
                <a href="ch14.html">
            
                    
                    14 数值
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.17" data-path="ch15.html">
            
                <a href="ch15.html">
            
                    
                    15 并发
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.18" data-path="ch16.html">
            
                <a href="ch16.html">
            
                    
                    16 历史及兼容性
            
                </a>
            

            
        </li>
    
        <li class="chapter " data-level="1.19" data-path="idx.html">
            
                <a href="idx.html">
            
                    
                    索引
            
                </a>
            

            
        </li>
    

    

    <li class="divider"></li>

    <li>
        <a href="https://github.com/honkit/honkit" target="blank" class="gitbook-link">
            本书使用 HonKit 发布
        </a>
    </li>
</ul>


                </nav>
            
        
    </div>

    <div class="book-body">
        
            <div class="body-inner">
                
                    

<div class="book-header" role="navigation">
    

    <!-- Title -->
    <h1>
        <i class="fa fa-circle-o-notch fa-spin"></i>
        <a href="." >6 模板</a>
    </h1>
</div>




                    <div class="page-wrapper" tabindex="-1" role="main">
                        <div class="page-inner">
                            
<div id="book-search-results">
    <div class="search-noresults">
    
                                <section class="normal markdown-section">
                                

    
        
                                <p><a class="en-page-number" id="79"></a></p>
<div class="chapter-number"><p class="chapter-number">6</p></div>

<h1 id="templates">模板 </h1>
<blockquote>
<p>把你的引言放在这里。</p>
<p><span title="这是一段极妙的引言，作者的很多书籍中“模板”一章都用了这段，这句话出现在“引言”位置，同时告诉你它可以被替换，所以这个引言本身就是一个“模板”" 。>—— B. Stroustrup</span><sup><a href="#fn_1" id="reffn_1">1</a></sup></p>
</blockquote>
<h2 id="6.1">6.1 导言 </h2>
<p>需要 vector 的人不太可能总是想要一个<code>double</code>的vector。
vector 是个泛化的概念，它本身独立于浮点数的概念存在。
因此，vector 的元素类型也应该具有独立的表现形式。
<em>模板（template）</em>是一种类或者函数，我们用一组类型或值去参数化它。
我们用模板表示这样一种概念：
它是某种通用的东西，我么可以通过指定参数来生成类型或函数，
至于这种参数，比方说是<code>vector</code>的元素类型<code>double</code>。</p>
<h2 id="6.2">6.2 参数化类型 </h2>
<p>我们那个 承载double的vector，可以泛化成一个 承载任意类型的vector，
只要把它变成一个<code>template</code>，并用一个类型参数替代具体的<code>double</code>类型。例如：</p>
<p><a class="en-page-number" id="80"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">private</span>:
    T* elem;        <span class="hljs-comment">// elem指向一个数组，该数组承载sz个T类型的元素</span>
    <span class="hljs-type">int</span> sz;
<span class="hljs-keyword">public</span>:
    <span class="hljs-function"><span class="hljs-keyword">explicit</span> <span class="hljs-title">Vector</span><span class="hljs-params">(<span class="hljs-type">int</span> s)</span></span>;         <span class="hljs-comment">// 构造函数：建立不变式，申请资源</span>
     ̃<span class="hljs-built_in">Vector</span>() { <span class="hljs-keyword">delete</span>[] elem; }    <span class="hljs-comment">// 析构函数：释放资源</span>

    <span class="hljs-comment">// ... 复制和移动操作 ...</span>

    T&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i);               <span class="hljs-comment">// 为非const Vector取下标元素</span>
    <span class="hljs-type">const</span> T&amp; <span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i) <span class="hljs-type">const</span>;   <span class="hljs-comment">// 为const Vector取下标元素(§4.2.1)</span>
    <span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> sz; }
};
</code></pre>
<p>前缀<code>template&lt;typename T&gt;</code>把<code>T</code>作为紧跟在它后面的声明的参数。
这是数学上“对所有T”的C++版本，或者更确切的说是“对于所有类型T”。
如果你想要数学上的“对所有T，有P(T)”，那你需要<em>概束（concept）</em>(§6.2.1, §7.2)。
用<code>class</code>引入类型参数与<code>typename</code>是等效的，
在旧式代码里<code>template&lt;class T&gt;</code>做前缀很常见。</p>
<p>成员函数可能有相似的定义：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt; Vector&lt;T&gt;::<span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span> s)
{
    <span class="hljs-keyword">if</span> (s&lt;<span class="hljs-number">0</span>)
        <span class="hljs-keyword">throw</span> Negative_size{};
    elem = <span class="hljs-keyword">new</span> T[s];
    sz = s;
}

<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-type">const</span> T&amp; Vector&lt;T&gt;::<span class="hljs-keyword">operator</span>[](<span class="hljs-type">int</span> i) <span class="hljs-type">const</span>
{
    <span class="hljs-keyword">if</span> (i&lt;<span class="hljs-number">0</span> || <span class="hljs-built_in">size</span>()&lt;=i)
        <span class="hljs-keyword">throw</span> out_of_range{<span class="hljs-string">&quot;Vector::operator[]&quot;</span>};
    <span class="hljs-keyword">return</span> elem[i];
}
</code></pre>
<p>有了以上这些定义，我们可以定义如下这些 <code>Vector</code>：</p>
<pre><code class="lang-cpp"><span class="hljs-function">Vector&lt;<span class="hljs-type">char</span>&gt; <span class="hljs-title">vc</span><span class="hljs-params">(<span class="hljs-number">200</span>)</span></span>;       <span class="hljs-comment">// 承载200个字符的 vector</span>
<span class="hljs-function">Vector&lt;string&gt; <span class="hljs-title">vs</span><span class="hljs-params">(<span class="hljs-number">17</span>)</span></span>;      <span class="hljs-comment">// 承载17个string的 vector</span>
Vector&lt;list&lt;<span class="hljs-type">int</span>&gt;&gt; <span class="hljs-built_in">vli</span>(<span class="hljs-number">45</span>);  <span class="hljs-comment">// 承载45个int列表的的 vector</span>
</code></pre>
<p><code>Vector&lt;list&lt;int&gt;&gt;</code>里的<code>&gt;&gt;</code>是嵌套模板参数的结尾；并非放错地方的输入运算符。</p>
<p>可这样使用<code>Vector</code>：</p>
<p><a class="en-page-number" id="81"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">write</span><span class="hljs-params">(<span class="hljs-type">const</span> Vector&lt;string&gt;&amp; vs)</span>    <span class="hljs-comment">// Vector of some strings</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; i!=vs.<span class="hljs-built_in">size</span>(); ++i)
        cout &lt;&lt; vs[i] &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
}
</code></pre>
<p>想让我们的<code>Vecor</code>支持区间-<code>for</code>循环，就必须定义适当的<code>begin()</code>和<code>end()</code>函数：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
T* <span class="hljs-title">begin</span><span class="hljs-params">(Vector&lt;T&gt;&amp; x)</span>
</span>{
    <span class="hljs-keyword">return</span> x.<span class="hljs-built_in">size</span>() ? &amp;x[<span class="hljs-number">0</span>] : <span class="hljs-literal">nullptr</span>;  <span class="hljs-comment">// 指向第一个元素的指针或者nullptr</span>
}

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
T* <span class="hljs-title">end</span><span class="hljs-params">(Vector&lt;T&gt;&amp; x)</span>
</span>{
    <span class="hljs-keyword">return</span> x.<span class="hljs-built_in">size</span>() ? &amp;x[<span class="hljs-number">0</span>]+x.<span class="hljs-built_in">size</span>() : <span class="hljs-literal">nullptr</span>; <span class="hljs-comment">// 指向末尾元素身后位置</span>
}
</code></pre>
<p>有了以上这些，就可以这样写：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f2</span><span class="hljs-params">(Vector&lt;string&gt;&amp; vs)</span> <span class="hljs-comment">// 某种东西的 Vector</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span>&amp; s : vs)
        cout &lt;&lt; s &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
}
</code></pre>
<p>同理，可以把list、vector、map（也就是关联数组）、
unordered map（也就是哈希表）等都定义为模板（第11章）。</p>
<p>模板是个编译期机制，因此使用它们跟手写的代码相比，并不会在运行时带来额外的负担。
实际上，<code>Vector&lt;double&gt;</code>生成的代码与第4章<code>Vector</code>版本的代码一致。
更进一步，标准库<code>vector&lt;double&gt;</code>生成的代码很可能更好
（因为实现它的时候下了更多功夫）。</p>
<p>模板附带一组模板参数，叫做<em>实例化（instantiation）</em>或者
<em>特化（specialization）</em>。
编译过程靠后的部分，在<em>实例化期（instantiation time）</em>，
程序里用到的每个实例都会被生成(§7.5)。
生成的代码会经历类型检查，以便它们与手写代码具有同样的类型安全性。
遗憾的是，此种类型检查通常处于编译过程较晚的阶段——在实例化期。</p>
<h3 id="6.2.1">6.2.1 受限模板参数（C++20） </h3>
<p>绝大多数情况下，只有当模板参数符合特定条件的时候，这个模板才说得通。
例如：<code>Vector</code>通常提供复制操作，如果它确实提供了，就必须要求其元素是可复制的。
这样，我们就得要求<code>Vector</code>的模板参数不仅仅是<code>typename</code>，而是一个<code>Element</code>，
其中的“<code>Element</code>”规定了一个作为元素的类型所需要满足的需求：</p>
<p><a class="en-page-number" id="82"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;Element T&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">private</span>:
    T* elem;    <span class="hljs-comment">// elem指向一个数组，该数组承载sz个T类型的元素</span>
    <span class="hljs-type">int</span> sz;
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>前缀<code>template&lt;Element T&gt;</code>就是数学中“对所有令<code>Element(T)</code>为真的T”的C++版本；
就是说，<code>Element</code>是个谓词，用来检测<code>T</code>，判断它是否具有<code>Vector</code>要求的全部属性。
这种谓词被称为<em>概束（concept）</em>(§7.2)。
指定过概束的模板参数被称为<em>受限参数（constrained argument）</em>，
参数受限的模板被称为<em>受限模板（constrained template）</em>。</p>
<p>如果用于实例化模板的类型不满足需求，会触发一个编译期错误。例如：</p>
<pre><code class="lang-cpp">Vector&lt;<span class="hljs-type">int</span>&gt; v1;     <span class="hljs-comment">// OK：可以复制一个int</span>
Vector&lt;thread&gt; v2;  <span class="hljs-comment">// 报错：不能复制标准线程 (§15.2)</span>
</code></pre>
<p>因为在 C++20 之前，C++没有官方支持概束，
较老的代码采用了未约束模板，而把需求内容留在了文档中。</p>
<h3 id="6.2.2">6.2.2 值模板参数 </h3>
<p>除了类型参数，模板还可以接受值参数。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T, <span class="hljs-type">int</span> N&gt;
<span class="hljs-keyword">struct</span> <span class="hljs-title class_">Buffer</span> {
    <span class="hljs-keyword">using</span> value_type = T;
    <span class="hljs-function"><span class="hljs-keyword">constexpr</span> <span class="hljs-type">int</span> <span class="hljs-title">size</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> N; }
    T[N];
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>别名（<code>value_type</code>）和<code>constexpr</code>函数允许我们（只读）访问模板参数。</p>
<p>值参数在很多语境里都很有用。
例如：<code>Buffer</code>允许我们创建任意容量的缓冲区，却不使用自由存储区（动态内存）：</p>
<pre><code class="lang-cpp">Buffer&lt;<span class="hljs-type">char</span>,<span class="hljs-number">1024</span>&gt; glob; <span class="hljs-comment">// 用于字符的全局缓冲区（静态分配）</span>

<span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">fct</span><span class="hljs-params">()</span> </span>{
    Buffer&lt;<span class="hljs-type">int</span>,<span class="hljs-number">10</span>&gt; buf; <span class="hljs-comment">// 用于整数的局部缓冲区（在栈上）</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>值模板参数必须是常量表达式。</p>
<p><a class="en-page-number" id="83"></a></p>
<h3 id="6.2.3">6.2.3 模板参数推导 </h3>
<p>考虑一下标准库模板<code>pair</code>的应用：</p>
<pre><code class="lang-cpp">pair&lt;<span class="hljs-type">int</span>,<span class="hljs-type">double</span>&gt; p = {<span class="hljs-number">1</span>,<span class="hljs-number">5.2</span>};
</code></pre>
<p>很多人发现要指定模板参数类型很烦冗，因此标准库提供了一个函数<code>make_pair()</code>，
以便借助其函数参数，推导其返回的<code>pair</code>的模板参数。：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">auto</span> p = <span class="hljs-built_in">make_pair</span>(<span class="hljs-number">1</span>,<span class="hljs-number">5.2</span>);  <span class="hljs-comment">// p 是个 pair&lt;int,double&gt;</span>
</code></pre>
<p>这就导致一个明显的疑问“为什么不直接通过构造函数的参数推导模板参数呢？”，
因此在C++17里，就可以了。这样：</p>
<pre><code class="lang-cpp">pair p = {<span class="hljs-number">1</span>,<span class="hljs-number">5.2</span>};   <span class="hljs-comment">// p 是个 pair&lt;int,double&gt;</span>
</code></pre>
<p>这不仅是<code>pair</code>的问题；<code>make_</code>函数的应用很常见。考虑如下这个简单的例子：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Vector</span>(<span class="hljs-type">int</span>);
    <span class="hljs-built_in">Vector</span>(initializer_list&lt;T&gt;);   <span class="hljs-comment">// 初始化列表构造函数</span>
    <span class="hljs-comment">// ...</span>
};

Vector v1 {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>};  <span class="hljs-comment">// 从初始值类型推导v1的元素类型</span>
Vector v2 = v1;     <span class="hljs-comment">// 从v1的元素类型推导v2的元素类型</span>

<span class="hljs-keyword">auto</span> p = <span class="hljs-keyword">new</span> Vector{<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>}; <span class="hljs-comment">// p 指向一个 Vector&lt;int&gt;</span>

<span class="hljs-function">Vector&lt;<span class="hljs-type">int</span>&gt; <span class="hljs-title">v3</span><span class="hljs-params">(<span class="hljs-number">1</span>)</span></span>;  <span class="hljs-comment">// 此处，我们需要显式指定元素类型（未提及元素类型）</span>
</code></pre>
<p>显然，这简化了拼写，并消除了因误拼冗余的模板参数类型而导致的烦躁。
不过，它并非万全之策。
模板参数推导可能会令人诧异（无论<code>make_</code>函数还是构造函数）。考虑：</p>
<pre><code class="lang-cpp">Vector&lt;string&gt; vs1 {<span class="hljs-string">&quot;Hello&quot;</span>, <span class="hljs-string">&quot;World&quot;</span>};  <span class="hljs-comment">// Vector&lt;string&gt;</span>
Vector vs {<span class="hljs-string">&quot;Hello&quot;</span>, <span class="hljs-string">&quot;World&quot;</span>};           <span class="hljs-comment">// 推导为 Vector&lt;const char*&gt; （诧异吗？）</span>
Vector vs2 {<span class="hljs-string">&quot;Hello&quot;</span>s, <span class="hljs-string">&quot;World&quot;</span>s};        <span class="hljs-comment">// 推导为 Vector&lt;string&gt;</span>
Vector vs3 {<span class="hljs-string">&quot;Hello&quot;</span>s, <span class="hljs-string">&quot;World&quot;</span>};         <span class="hljs-comment">// 报错：初始化列表类型不单一</span>
</code></pre>
<p>C-风格字符串文本值的类型是<code>const const*</code>(§1.7.1)。
如果这不符合意图，请用一个<code>s</code>后缀，让它明确成为<code>string</code>(§9.2)。
如果初始化列表中具有不同类型，就无法推导出一个单一类型，因此会报错。</p>
<p>如果无法从构造函数参数推导某个模板参数，
我们可以用<em>推导引导（deduction guide）</em>辅助。考虑：</p>
<p><a class="en-page-number" id="84"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector2</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-keyword">using</span> value_type = T;
    <span class="hljs-comment">// ...</span>
    <span class="hljs-built_in">Vector2</span>(initializer_list&lt;T&gt;);   <span class="hljs-comment">// 初始化列表构造函数</span>

    <span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Iter&gt;
        <span class="hljs-title">Vector2</span><span class="hljs-params">(Iter b, Iter e)</span></span>;    <span class="hljs-comment">// [b:e) 区间构造函数</span>
    <span class="hljs-comment">// ...</span>
};

Vector2 v1 {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>};             <span class="hljs-comment">// 元素类型是 int</span>
<span class="hljs-function">Vector2 <span class="hljs-title">v2</span><span class="hljs-params">(v1.begin(),v1.begin()+<span class="hljs-number">2</span>)</span></span>;
</code></pre>
<p>很明显，<code>v2</code>应该是个<code>Vector2&lt;int&gt;</code>，但是因为缺少辅助信息，编译器无法推导出来。
这段代码仅表明：有个构造函数接收一对同类型的值。
缺乏概束(§7.2)的语言支持，对于该类型，编译器无法假设任何情况。
如果想进行推导，可以在<code>Vector2</code>的声明后添加一个<em>推导指引（deduction guide）</em>：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Iter&gt;
    <span class="hljs-title">Vector2</span><span class="hljs-params">(Iter,Iter)</span> -&gt; Vector2&lt;<span class="hljs-keyword">typename</span> Iter::value_type&gt;</span>;
</code></pre>
<p>意思是，如果我们看到<code>Vector2</code>使用一对迭代器初始化，
应该把<code>Vector2::value_type</code>推导为迭代器的值类型。</p>
<p>推导指引的效果通常很微妙，因此在设计类模板的时候，尽量别依靠它。
不过，标准库里满是（目前还）未使用<code>concept</code>且带有这种二义性的类，
因此它们用了不少的推导指引。</p>
<h2 id="6.3">6.3 参数化操作 </h2>
<p>除了用元素类型参数化容器，模板还有很多别的用途。
具体来说，它们被广泛用于泛化标准库中的类型和算法(§11.6, §12.6)。</p>
<p>表示一个操作被类型或值泛化，有三种方式：</p>
<ul>
<li>函数模板</li>
<li>函数对象：一个可以携带数据的对象，且像函数一样被调用</li>
<li>lambda表达式：函数对象的快捷形式</li>
</ul>
<h3 id="6.3.1">6.3.1 函数模板 </h3>
<p>可以写一个元素求和函数，针对可以利用 区间-<code>for</code>
遍历的任意序列（也就是容器），像这样：</p>
<p><a class="en-page-number" id="85"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Sequence, <span class="hljs-keyword">typename</span> Value&gt;
Value <span class="hljs-title">sum</span><span class="hljs-params">(<span class="hljs-type">const</span> Sequence&amp; s, Value v)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span> x : s)
        v+=x;
    <span class="hljs-keyword">return</span> v;
}
</code></pre>
<p>模板参数<code>Value</code>和函数参数<code>v</code>，
允许调用者指定这个累加函数的类型和初值（累加到和里的变量）：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user</span><span class="hljs-params">(Vector&lt;<span class="hljs-type">int</span>&gt;&amp; vi, list&lt;<span class="hljs-type">double</span>&gt;&amp; ld, vector&lt;complex&lt;<span class="hljs-type">double</span>&gt;&gt;&amp; vc)</span>
</span>{
    <span class="hljs-type">int</span> x = <span class="hljs-built_in">sum</span>(vi,<span class="hljs-number">0</span>);                  <span class="hljs-comment">// 承载 int 的vector的和（与 int 相加）</span>
    <span class="hljs-type">double</span> d = <span class="hljs-built_in">sum</span>(vi,<span class="hljs-number">0.0</span>);             <span class="hljs-comment">// 承载 int 的vector的和（与 double 相加）</span>
    <span class="hljs-type">double</span> dd = <span class="hljs-built_in">sum</span>(ld,<span class="hljs-number">0.0</span>);            <span class="hljs-comment">// 承载 double 的vector的和</span>
    <span class="hljs-keyword">auto</span> z = <span class="hljs-built_in">sum</span>(vc,complex{<span class="hljs-number">0.0</span>,<span class="hljs-number">0.0</span>});  <span class="hljs-comment">// 承载 complex&lt;double&gt;s 的vector的和</span>
}
</code></pre>
<p>把<code>int</code>加到<code>double</code>上的意义在于能优雅地处理超出<code>int</code>上限地数值。
注意<code>sum&lt;Sequence,Value&gt;</code>从函数参数中推导模板参数的方法。
巧的是不需要显式指定它们。</p>
<p>这个<code>sum()</code>是标准库里<code>accumulate()</code>(§14.3)的简化版本。</p>
<p>函数模板可用于成员函数，但不能是<code>virtual</code>成员。
在一个程序里，编译器无法知晓某个模板的全部实例，因此无法生成<code>vtbl</code>(§4.4)。</p>
<h3 id="6.3.2">6.3.2 函数对象 </h3>
<p>有一种特别有用的模板是<em>函数对象（function object）</em>
（也叫<em>仿函数（functor）</em>），用于定义可调用对象。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Less_than</span> {
    <span class="hljs-type">const</span> T val;    <span class="hljs-comment">// 参与比对的值</span>
<span class="hljs-keyword">public</span>:
    <span class="hljs-built_in">Less_than</span>(<span class="hljs-type">const</span> T&amp; v) :val{v} { }
    <span class="hljs-function"><span class="hljs-type">bool</span> <span class="hljs-title">operator</span><span class="hljs-params">()</span><span class="hljs-params">(<span class="hljs-type">const</span> T&amp; x)</span> <span class="hljs-type">const</span> </span>{ <span class="hljs-keyword">return</span> x&lt;val; } <span class="hljs-comment">// 调用运算符</span>
};
</code></pre>
<p>名为<code>operator()</code>的函数实现“函数调用”、“调用”或“应用”运算符<code>()</code>。</p>
<p>可以为某些参数类型定义<code>Less_than</code>类型的具名变量：</p>
<pre><code class="lang-cpp">Less_than lti {<span class="hljs-number">42</span>};                 <span class="hljs-comment">// lti(i) 将把i用&lt;号与42作比（i&lt;42）</span>
Less_than lts {<span class="hljs-string">&quot;Backus&quot;</span>s};          <span class="hljs-comment">// lts(s) 将把s用&lt;号与&quot;Backus&quot;作比（s&lt;&quot;Backus&quot;）</span>
Less_than&lt;string&gt; lts2 {<span class="hljs-string">&quot;Naur&quot;</span>};    <span class="hljs-comment">// &quot;Naur&quot;是个C风格字符串，因此需要用 &lt;string&gt; 获取正确的 &lt;</span>
</code></pre>
<p>可以像调用函数一样调用这样的对象：</p>
<p><a class="en-page-number" id="86"></a></p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">fct</span><span class="hljs-params">(<span class="hljs-type">int</span> n, <span class="hljs-type">const</span> string&amp; s)</span>
</span>{
    <span class="hljs-type">bool</span> b1 = <span class="hljs-built_in">lti</span>(n); <span class="hljs-comment">// true if n&lt;42</span>
    <span class="hljs-type">bool</span> b2 = <span class="hljs-built_in">lts</span>(s); <span class="hljs-comment">// true if s&lt;&quot;Backus&quot;</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>这种函数对象广泛用做算法的参数。例如，可以统计使特定谓词为<code>true</code>的值的数量：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> C, <span class="hljs-keyword">typename</span> P&gt;
    <span class="hljs-comment">// requires Sequence&lt;C&gt; &amp;&amp; Callable&lt;P,Value_type&lt;P&gt;&gt;</span>
<span class="hljs-function"><span class="hljs-type">int</span> <span class="hljs-title">count</span><span class="hljs-params">(<span class="hljs-type">const</span> C&amp; c, P pred)</span>
</span>{
    <span class="hljs-type">int</span> cnt = <span class="hljs-number">0</span>;
    <span class="hljs-keyword">for</span> (<span class="hljs-type">const</span> <span class="hljs-keyword">auto</span>&amp; x : c)
        <span class="hljs-keyword">if</span> (<span class="hljs-built_in">pred</span>(x))
            ++cnt;
    <span class="hljs-keyword">return</span> cnt;
}
</code></pre>
<p><em>谓词（predicate）</em>是调用后能返回<code>true</code>或<code>false</code>的东西。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">const</span> Vector&lt;<span class="hljs-type">int</span>&gt;&amp; vec, <span class="hljs-type">const</span> list&lt;string&gt;&amp; lst, <span class="hljs-type">int</span> x, <span class="hljs-type">const</span> string&amp; s)</span>
</span>{
    cout &lt;&lt; <span class="hljs-string">&quot;number of values less than &quot;</span> &lt;&lt; x &lt;&lt; <span class="hljs-string">&quot;: &quot;</span> &lt;&lt; <span class="hljs-built_in">count</span>(vec,Less_than{x}) &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
    cout &lt;&lt; <span class="hljs-string">&quot;number of values less than &quot;</span> &lt;&lt; s &lt;&lt; <span class="hljs-string">&quot;: &quot;</span> &lt;&lt; <span class="hljs-built_in">count</span>(lst,Less_than{s}) &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
}
</code></pre>
<p>此处，<code>Less_than{x}</code>构造一个<code>Less_than&lt;int&gt;</code>类型的对象，
它的调用运算符会与名为<code>x</code>的<code>int</code>进行比较；
<code>Less_than{s}</code>会构造一个对象，与名为<code>s</code>的<code>string</code>进行比较。
这些函数对象的妙处在于，它们随身携带参与比较的值。
我们无需为每个值（以及每种类型）写一个单独的函数，
也无需引入一个恼人的全局变量去持有这个值。
还有，类似于<code>Less_than</code>这种函数对象易于内联，
因此调用<code>Less_than</code>远比间接的函数调用高效。
携带数据的能力再加上高效性，使函数对象作为算法参数特别有用。</p>
<p>用在通用算法中的函数对象，可指明其关键运算的意义
（例如<code>Less_than</code>之于<code>count()</code>），通常被称为<em>策略对象（policy object）</em>。</p>
<h3 id="6.3.3">6.3.3 Lambda表达式 </h3>
<p>在 §6.3.2 中，我们把<code>Less_than</code>的定义和它的应用拆开了。这样不太方便。
你猜怎么着，还有个隐式生成函数对象的写法：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">f</span><span class="hljs-params">(<span class="hljs-type">const</span> Vector&lt;<span class="hljs-type">int</span>&gt;&amp; vec, <span class="hljs-type">const</span> list&lt;string&gt;&amp; lst, <span class="hljs-type">int</span> x, <span class="hljs-type">const</span> string&amp; s)</span>
</span>{
    cout &lt;&lt; <span class="hljs-string">&quot;number of values less than &quot;</span> &lt;&lt; x
        &lt;&lt; <span class="hljs-string">&quot;: &quot;</span> &lt;&lt; <span class="hljs-built_in">count</span>(vec,[&amp;](<span class="hljs-type">int</span> a){ <span class="hljs-keyword">return</span> a&lt;x; })
        &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
</code></pre>
<p><a class="en-page-number" id="87"></a></p>
<pre><code class="lang-cpp">    cout &lt;&lt; <span class="hljs-string">&quot;number of values less than &quot;</span> &lt;&lt; s
        &lt;&lt; <span class="hljs-string">&quot;: &quot;</span> &lt;&lt; <span class="hljs-built_in">count</span>(lst,[&amp;](<span class="hljs-type">const</span> string&amp; a){ <span class="hljs-keyword">return</span> a&lt;s; })
        &lt;&lt; <span class="hljs-string">&apos;\n&apos;</span>;
}
</code></pre>
<p><code>[&amp;](int a){return a&lt;x;}</code>这个写法叫<em>lambda表达式（lambda expression）</em>。
它跟<code>Less_than&lt;int&gt;{x}</code>一样会生成函数对象。
此处的<code>[&amp;]</code>是一个<em>抓取列表（capture list）</em>，
表明lambda函数体内用到的所有局部名称，将以引用的形式访问。
如果我们仅想“抓取”<code>x</code>，应该这么写：<code>[&amp;x]</code>。
如果我们把x的副本传给生成的对象，就应该这么写：<code>[=x]</code>。
不抓取任何东西写<code>[ ]</code>，以引用方式抓取所有局部名称写<code>[&amp;]</code>，
以传值方式抓取所有局部名称写：<code>[=]</code>。</p>
<p>使用lambda表达式方便、简略，但也略晦涩些。
对于繁复的操作（比方说超出一个表达式的内容），
我倾向于为它命名，以便明确用途，并让它可以在程序中多处访问。</p>
<p>在 §4.5.3 中，我们遇到了一个困扰，
就是在使用元素类型为指针或<code>unique_ptr</code>的<code>vector</code>时，
要写很多针对其元素的操作，比方说<code>draw_all()</code>和<code>rotate_all()</code>。
函数对象（确切的说是lambda表达式）有助于把容器遍历和针对每个元素的操作分离开。</p>
<p>首先，我们需要一个函数，操作指针容器中元素指向的对象：</p>
<pre><code class="lang-cpp">
<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> C, <span class="hljs-keyword">typename</span> Oper&gt;
<span class="hljs-type">void</span> <span class="hljs-title">for_all</span><span class="hljs-params">(C&amp; c, Oper op)</span> <span class="hljs-comment">// 假定C是个承载指针的容器</span>
    <span class="hljs-comment">// 要求 Sequence&lt;C&gt; &amp;&amp; Callable&lt;Oper,Value_type&lt;C&gt;&gt; (see §7.2.1)</span>
</span>{
    <span class="hljs-keyword">for</span> (<span class="hljs-keyword">auto</span>&amp; x : c)
        <span class="hljs-built_in">op</span>(x);  <span class="hljs-comment">// 把每个元素指向的对象传引用给 op()</span>
}
</code></pre>
<p>现在，针对 §4.5 中的 <code>user()</code>，可以写个不需要一堆<code>_all</code>函数的版本了：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user2</span><span class="hljs-params">()</span>
</span>{
    vector&lt;unique_ptr&lt;Shape&gt;&gt; v;
    <span class="hljs-keyword">while</span> (cin)
        v.<span class="hljs-built_in">push_back</span>(<span class="hljs-built_in">read_shape</span>(cin));
    for_all(v,[](unique_ptr&lt;Shape&gt;&amp; ps){ ps-&gt;<span class="hljs-built_in">draw</span>(); });        <span class="hljs-comment">// draw_all()</span>
    for_all(v,[](unique_ptr&lt;Shape&gt;&amp; ps){ ps-&gt;<span class="hljs-built_in">rotate</span>(<span class="hljs-number">45</span>); });    <span class="hljs-comment">// rotate_all(45)</span>
}
</code></pre>
<p>我把<code>unique_ptr&lt;Shape&gt;&amp;</code>传给lambda表达式，
这样<code>for_all()</code>就无需关心对象存储的方式了。
确切的说，这些<code>for_all()</code>函数不影响传入的<code>Shape</code>生命期，
lambda表达式的函数体使用参数时，就像用旧式的指针一样。</p>
<p>跟函数一样，lambda表达式也可以泛型。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">class</span> S&gt;
<span class="hljs-type">void</span> <span class="hljs-title">rotate_and_draw</span><span class="hljs-params">(vector&lt;S&gt;&amp; v, <span class="hljs-type">int</span> r)</span>
</span>{
    for_all(v,[](<span class="hljs-keyword">auto</span>&amp; s){ s-&gt;<span class="hljs-built_in">rotate</span>(r); s-&gt;<span class="hljs-built_in">draw</span>(); });
}
</code></pre>
<p><a class="en-page-number" id="88"></a></p>
<p>此处的<code>auto</code>，像变量声明里那样，
意思是初始值（在调用中，实参初始化形参）接受任何类型。
这让带有<code>auto</code>的lambda表达式成了模板，一个<em>泛型lambda（generic lambda）</em>。
由于标准委员会政策方面的疏漏，此种<code>auto</code>的应用，目前无法用于函数参数。</p>
<p>可以用任意容器调用这个泛型的<code>rotate_and_draw()</code>，
只要该容器内的对象能执行<code>draw()</code>和<code>rotate()</code>。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-type">void</span> <span class="hljs-title">user4</span><span class="hljs-params">()</span>
</span>{
    vector&lt;unique_ptr&lt;Shape&gt;&gt; v1;
    vector&lt;Shape*&gt; v2;
    <span class="hljs-comment">// ...</span>
    <span class="hljs-built_in">rotate_and_draw</span>(v1,<span class="hljs-number">45</span>);
    <span class="hljs-built_in">rotate_and_draw</span>(v2,<span class="hljs-number">90</span>);
}
</code></pre>
<p>利用lambda表达式，可以把任何语句变成表达式。
其主要用途是，把用于求值的运算当作参数值传递，但它的能力是通用的。
考虑以下这个复杂的初始化：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">enum class</span> <span class="hljs-title class_">Init_mode</span> { zero, seq, cpy, patrn }; <span class="hljs-comment">// 各种初始化方式</span>

<span class="hljs-comment">// 乱糟糟的代码：</span>

<span class="hljs-comment">// int n, Init_mode m, vector&lt;int&gt;&amp; arg, 和 iterators p 以及 q 在别处定义</span>

vector&lt;<span class="hljs-type">int</span>&gt; v;

<span class="hljs-keyword">switch</span> (m) {
<span class="hljs-keyword">case</span> zero:
    v = <span class="hljs-built_in">vector</span>&lt;<span class="hljs-type">int</span>&gt;(n); <span class="hljs-comment">// n个初始化为0的元素</span>
    <span class="hljs-keyword">break</span>;
<span class="hljs-keyword">case</span> cpy:
    v = arg;
    <span class="hljs-keyword">break</span>;
};

<span class="hljs-comment">// ...</span>

<span class="hljs-keyword">if</span> (m == seq)
    v.<span class="hljs-built_in">assign</span>(p,q);      <span class="hljs-comment">// 从序列[p:q)复制</span>

<span class="hljs-comment">// ...</span>
</code></pre>
<p>这是个风格化明显的例子，但不幸的是这种情况并不罕见。
我们要在一组初始化方法中进行选择，
去初始化一个数据结构（此处是<code>v</code>），并为不同方法做不同的运算。
这种代码通常一塌糊涂，声称“为了高效”必不可少，还是bug之源：</p>
<ul>
<li>变量可能在获得合适的值之前被使用</li>
<li>“初始化代码”跟其它代码混杂在一起，以至于难以理解</li>
</ul>
<p><a class="en-page-number" id="89"></a></p>
<ul>
<li>在“初始化代码”与其它代码混杂的时候，很容易缺失某个case</li>
<li>这不是初始化，而是赋值</li>
</ul>
<p>取而代之，可以把它转化为一个lambda表达式，用作初值：</p>
<pre><code class="lang-cpp"><span class="hljs-comment">// int n, Init_mode m, vector&lt;int&gt;&amp; arg, 和 iterators p 以及 q 在别处定义</span>

vector&lt;<span class="hljs-type">int</span>&gt; v = [&amp;] {
    <span class="hljs-keyword">switch</span> (m) {
    <span class="hljs-keyword">case</span> zero:
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">vector</span>&lt;<span class="hljs-type">int</span>&gt;(n);      <span class="hljs-comment">// n个初始化为0的元素</span>
    <span class="hljs-keyword">case</span> seq:
        <span class="hljs-keyword">return</span> vector&lt;<span class="hljs-type">int</span>&gt;{p,q};    <span class="hljs-comment">// 从序列[p:q)复制</span>
    <span class="hljs-keyword">case</span> cpy:
        <span class="hljs-keyword">return</span> arg;
    }
}();
<span class="hljs-comment">// ...</span>
</code></pre>
<p>我仍然“忘掉”了一个<code>case</code>，不过现在这就不难察觉了。</p>
<h2 id="6.4">6.4 模板机制 </h2>
<p>要定义出好的模板，我们需要一些辅助的语言构造：</p>
<ul>
<li>依赖于类型的值：<em>变量模板（variable template）</em>(§6.4.1)。</li>
<li>针对类型和模板的别名：<em>别名模板（alias template）</em>(§6.4.2)。</li>
<li>编译期选择机制：<code>if constexpr</code>(§6.4.3)。</li>
<li>针对类型和表达式属性的编译期查询机制：<code>requires</code>表达式(§7.2.3)。</li>
</ul>
<p>另外，<code>constexpr</code>函数(§1.6)和<code>static_asserts</code>(§3.5.5)
也经常参与模板设计和应用。</p>
<p>对于构建通用、基本的抽象，这些基础机制是主要工具。</p>
<h3 id="6.4.1">6.4.1 变量模板 </h3>
<p>在使用某个类型时，经常会需要该类型的常量和值。
这理所当然也发生在我们使用类模板的的时候：
当我们定义了<code>C&lt;T&gt;</code>，通常会需要类型<code>T</code>以及依赖<code>T</code>的其它类型的常量和变量。
以下示例出自一个流体力学模拟<span title="一篇论文《Humanitarian security regimes》，网址是：https://doi.org/10.1111/1468-2346.12186">[Garcia,2015]</span><sup><a href="#fn_2" id="reffn_2">2</a></sup>：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>&gt;
    <span class="hljs-keyword">constexpr</span> T viscosity = <span class="hljs-number">0.4</span>;

<span class="hljs-keyword">template</span> &lt;<span class="hljs-keyword">class</span> <span class="hljs-title class_">T</span>&gt;
    <span class="hljs-keyword">constexpr</span> space_vector&lt;T&gt; external_acceleration = { T{}, T{<span class="hljs-number">-9.8</span>}, T{} };
<span class="hljs-keyword">auto</span> vis2 = <span class="hljs-number">2</span>*viscosity&lt;<span class="hljs-type">double</span>&gt;;
<span class="hljs-keyword">auto</span> acc = external_acceleration&lt;<span class="hljs-type">float</span>&gt;;
</code></pre>
<p>此处的<code>space_vector</code>是个三维向量。</p>
<p><a class="en-page-number" id="90"></a></p>
<p>显然，可以用适当类型的任意表达式作为初始值。考虑：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T, <span class="hljs-keyword">typename</span> T2&gt;
    <span class="hljs-keyword">constexpr</span> <span class="hljs-type">bool</span> Assignable = 
        is_assignable&lt;T&amp;,T2&gt;::value; <span class="hljs-comment">// is_assignable 是个类型 trait (§13.9.1)</span>

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-type">void</span> <span class="hljs-title">testing</span><span class="hljs-params">()</span>
</span>{
    <span class="hljs-built_in">static_assert</span>(Assignable&lt;T&amp;,<span class="hljs-type">double</span>&gt;, <span class="hljs-string">&quot;can&apos;t assign a double&quot;</span>);
    <span class="hljs-built_in">static_assert</span>(Assignable&lt;T&amp;,string&gt;, <span class="hljs-string">&quot;can&apos;t assign a string&quot;</span>);
}
</code></pre>
<p>经历一些大刀阔斧的变动，这个点子成了概束定义(§7.2)的关键。</p>
<h3 id="6.4.2">6.4.2 别名 </h3>
<p>出人意料的是，为类型或者模板引入一个同义词很有用。
例如，标准库头文件<code>&lt;cstddef&gt;</code>包含一个<code>size_t</code>的别名，可能是这样：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">using</span> <span class="hljs-type">size_t</span> = <span class="hljs-type">unsigned</span> <span class="hljs-type">int</span>;
</code></pre>
<p>用于命名<code>size_t</code>的实际类型是实现相关的，
因此在另一个实现里<code>size_t</code>可能是<code>unsigned long</code>。
有了别名<code>size_t</code>的存在，就让程序员能够写出可移植的代码。</p>
<p>对参数化类型来说，为模板参数相关的类型提供别名是很常见的。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Vector</span> {
<span class="hljs-keyword">public</span>:
    <span class="hljs-keyword">using</span> value_type = T;
    <span class="hljs-comment">// ...</span>
};
</code></pre>
<p>实际上，每个标准库容器都提供了<code>value_type</code>作为其值类型的名称（第11章）。
对于所有遵循此惯例的容器，我们都能写出可行的代码。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> C&gt;
<span class="hljs-keyword">using</span> Value_type = <span class="hljs-keyword">typename</span> C::value_type;  <span class="hljs-comment">// C 的元素的类型</span>

<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Container&gt;
<span class="hljs-type">void</span> <span class="hljs-title">algo</span><span class="hljs-params">(Container&amp; c)</span>
</span>{
    Vector&lt;Value_type&lt;Container&gt;&gt; vec;      <span class="hljs-comment">// 结果保存在这里</span>
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p>通过绑定部分或全部模板参数，可以用别名机制定义一个新模板。例如：</p>
<p><a class="en-page-number" id="91"></a></p>
<pre><code class="lang-cpp"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Key, <span class="hljs-keyword">typename</span> Value&gt;
<span class="hljs-keyword">class</span> <span class="hljs-title class_">Map</span> {
    <span class="hljs-comment">// ...</span>
};

<span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> Value&gt;
<span class="hljs-keyword">using</span> String_map = Map&lt;string,Value&gt;;

String_map&lt;<span class="hljs-type">int</span>&gt; m;  <span class="hljs-comment">// m 是个 Map&lt;string,int&gt;</span>
</code></pre>
<h3 id="6.4.3">6.4.3 编译期<code>if</code> </h3>
<p>思考编写这样一个操作，它在<code>slow_and_safe(T)</code>和<code>simple_and_fast(T)</code>里二选一。
这种问题充斥在基础代码中——那些通用性和性能优化都重要的场合。
传统的解决方案是写一对重载的函数，并基于 trait(§13.9.1) 选出最适宜的那个，
比方说标准库里的<code>is_pod</code>。
如果涉及类体系，<code>slow_and_safe(T)</code>可提供通用操作，
而某个继承类可以用<code>simple_and_fast(T)</code>的实现去重载它。</p>
<p>在 C++17 里，可以利用一个编译期<code>if</code>：</p>
<pre><code class="lang-cpp">
<span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt; <span class="hljs-type">void</span> <span class="hljs-title">update</span><span class="hljs-params">(T&amp; target)</span>
</span>{
    <span class="hljs-comment">// ...</span>
    <span class="hljs-function"><span class="hljs-keyword">if</span> <span class="hljs-title">constexpr</span><span class="hljs-params">(is_pod&lt;T&gt;::value)</span>
        <span class="hljs-title">simple_and_fast</span><span class="hljs-params">(target)</span></span>;    <span class="hljs-comment">// 针对“简单旧式的数据”</span>
    <span class="hljs-keyword">else</span>
        <span class="hljs-built_in">slow_and_safe</span>(target);
    <span class="hljs-comment">// ...</span>
}
</code></pre>
<p><code>is_pod&lt;T&gt;</code>是个类型trait (§13.9.1)，它辨别某个类型可否低成本复制。</p>
<p>仅被选定的<code>if constexpr</code>分支被实例化。
此方案即提供了性能优化，又实现了优化的局部性。</p>
<p>重要的是，<code>if constexpr</code>并非文本处理机制，
不会破坏语法、类型和作用域的常见规则。例如：</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">template</span>&lt;<span class="hljs-keyword">typename</span> T&gt;
<span class="hljs-type">void</span> <span class="hljs-title">bad</span><span class="hljs-params">(T arg)</span>
</span>{
    <span class="hljs-function"><span class="hljs-keyword">if</span> <span class="hljs-title">constexpr</span><span class="hljs-params">(Something&lt;T&gt;::value)</span>
        <span class="hljs-keyword">try</span> </span>{                           <span class="hljs-comment">// 语法错误</span>

    <span class="hljs-built_in">g</span>(arg);

    <span class="hljs-function"><span class="hljs-keyword">if</span> <span class="hljs-title">constexpr</span><span class="hljs-params">(Something&lt;T&gt;::value)</span>
        } <span class="hljs-title">catch</span><span class="hljs-params">(...)</span> </span>{ <span class="hljs-comment">/* ... */</span> }      <span class="hljs-comment">// 语法错误</span>
}
</code></pre>
<p><a class="en-page-number" id="92"></a></p>
<p>如果允许类似的文本操作，会严重破坏代码的可靠性，而且对依赖于新型程序表示技术
（比方说“抽象语法树（abstract syntax tree）”）的工具，会造成问题。</p>
<h2 id="6.5">6.5 忠告 </h2>
<ul>
<li>[1] 可应用于很多参数类型的算法，请用模板去表达；§6.1; [CG: T.2]。</li>
<li>[2] 请用模板去表达容器；§6.2; [CG: T.3]。</li>
<li>[3] 请用模板提升代码的抽象层级；§6.2; [CG: T.1]。</li>
<li>[4] 模板是类型安全的，但它的类型检查略有些迟滞；§6.2。</li>
<li>[5] 让构造函数或者函数模板去推导模板参数类型；§6.2.3。</li>
<li>[6] 使用算法的时候，请用函数对象作参数；§6.3.2; [CG: T.40]‘</li>
<li>[7] 如果需要简单的一次性函数对象，采用lambda表达式；§6.3.2。</li>
<li>[8] 虚成员函数无法作为模板成员函数；§6.3.1。</li>
<li>[9] 用模板别名简化符号表示，并隐藏实现细节；§6.4.2。</li>
<li>[10] 使用模板时，确保其定义（不仅仅是声明）在作用域内；§7.5。</li>
<li>[11] 模板提供编译期的“鸭子类型（duck typing）”；§7.5。</li>
<li>[12] 模板不支持分离编译：把模板定义<code>#include</code>进每个用到它的编译单元。</li>
</ul>
<blockquote id="fn_1">
<sup>1</sup>. 这是一段极妙的引言，作者的很多书籍中“模板”一章都用了这段，这句话出现在“引言”位置，同时告诉你它可以被替换，所以这个引言本身就是一个“模板”。—— 译者啰嗦<a href="#reffn_1" title="Jump back to footnote [1] in the text."> ↩</a>
</blockquote>
<blockquote id="fn_2">
<sup>2</sup>. 一篇论文《Humanitarian security regimes》，网址是：<a href="https://doi.org/10.1111/1468-2346.12186" target="_blank">https://doi.org/10.1111/1468-2346.12186</a><a href="#reffn_2" title="Jump back to footnote [2] in the text."> ↩</a>
</blockquote>

<script>console.log(window.location.pathname)</script>
<div id="disqus_thread"></div>
<script>

/**
*  RECOMMENDED CONFIGURATION VARIABLES: EDIT AND UNCOMMENT THE SECTION BELOW TO INSERT DYNAMIC VALUES FROM YOUR PLATFORM OR CMS.
*  LEARN WHY DEFINING THESE VARIABLES IS IMPORTANT: https://disqus.com/admin/universalcode/#configuration-variables*/
/*
var disqus_config = function () {
this.page.url = window.location.href;
this.page.identifier = window.location.pathname;
};
*/
(function() { // DON'T EDIT BELOW THIS LINE
var d = document, s = d.createElement('script');
s.src = 'https://a-tour-of-cpp-2nd-cn.disqus.com/embed.js';
s.setAttribute('data-timestamp', +new Date());
(d.head || d.body).appendChild(s);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" target="_blank">comments powered by Disqus.</a></noscript>
                                
    

                                </section>
                            
    </div>
    <div class="search-results">
        <div class="has-results">
            
            <h1 class="search-results-title"><span class='search-results-count'></span> results matching "<span class='search-query'></span>"</h1>
            <ul class="search-results-list"></ul>
            
        </div>
        <div class="no-results">
            
            <h1 class="search-results-title">No results matching "<span class='search-query'></span>"</h1>
            
        </div>
    </div>
</div>

                        </div>
                    </div>
                
            </div>

            
                
                <a href="ch05.html" class="navigation navigation-prev " aria-label="Previous page: 5 基本操作">
                    <i class="fa fa-angle-left"></i>
                </a>
                
                
                <a href="ch07.html" class="navigation navigation-next " aria-label="Next page: 7 概束和泛型编程">
                    <i class="fa fa-angle-right"></i>
                </a>
                
            
        
    </div>

    <script>
        var gitbook = gitbook || [];
        gitbook.push(function() {
            gitbook.page.hasChanged({"page":{"ch":6,"title":"6 模板","level":"1.8","depth":1,"next":{"title":"7 概束和泛型编程","level":"1.9","depth":1,"path":"ch07.md","ref":"ch07.md","articles":[]},"previous":{"title":"5 基本操作","level":"1.7","depth":1,"path":"ch05.md","ref":"ch05.md","articles":[]},"dir":"ltr"},"config":{"plugins":["@dogatana/page-toc-button","@dogatana/back-to-top-button","copy-code-button","forkmegithub","disqus-legacy"],"root":"./src","styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"pluginsConfig":{"@dogatana/back-to-top-button":{},"styles":{"website":"styles/website.css"},"search":{},"@dogatana/page-toc-button":{},"lunr":{"maxIndexSize":1000000,"ignoreSpecialCharacters":false},"fontsettings":{"theme":"white","family":"sans","size":2},"highlight":{},"disqus-legacy":{"shortname":"a-tour-of-cpp-2nd-cn"},"copy-code-button":{},"forkmegithub":{"color":"orange","url":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"},"theme-default":{"styles":{"website":"styles/website.css","pdf":"styles/pdf.css","epub":"styles/epub.css","mobi":"styles/mobi.css","ebook":"styles/ebook.css","print":"styles/print.css"},"showLevel":false}},"theme":"default","author":"Windsting","pdf":{"pageNumbers":true,"fontSize":14,"fontFamily":"Arial","paperSize":"a5","chapterMark":"pagebreak","pageBreaksBefore":"/","margin":{"right":62,"left":62,"top":56,"bottom":56},"embedFonts":false},"structure":{"langs":"LANGS.md","readme":"README.md","glossary":"GLOSSARY.md","summary":"SUMMARY.md"},"variables":{},"title":"C++导览 第二版 简体中文版","language":"zh-hans","links":{"sidebar":{"Github Link":"https://github.com/windsting/a-tour-of-cpp-2nd-cn"}},"gitbook":"*","description":"A Tour of C++ (第二版) 非官方中译本"},"file":{"path":"ch06.md","mtime":"2023-10-27T09:36:56.982Z","type":"markdown"},"gitbook":{"version":"5.1.1","time":"2023-10-27T09:54:29.738Z"},"basePath":".","book":{"language":""}});
        });
    </script>
</div>

        
    <noscript>
        <style>
            .honkit-cloak {
                display: block !important;
            }
        </style>
    </noscript>
    <script>
        // Restore sidebar state as critical path for prevent layout shift
        function __init__getSidebarState(defaultValue){
            var baseKey = "";
            var key = baseKey + ":sidebar";
            try {
                var value = localStorage[key];
                if (value === undefined) {
                    return defaultValue;
                }
                var parsed = JSON.parse(value);
                return parsed == null ? defaultValue : parsed;
            } catch (e) {
                return defaultValue;
            }
        }
        function __init__restoreLastSidebarState() {
            var isMobile = window.matchMedia("(max-width: 600px)").matches;
            if (isMobile) {
                // Init last state if not mobile
                return;
            }
            var sidebarState = __init__getSidebarState(true);
            var book = document.querySelector(".book");
            // Show sidebar if it enabled
            if (sidebarState && book) {
                book.classList.add("without-animation", "with-summary");
            }
        }

        try {
            __init__restoreLastSidebarState();
        } finally {
            var book = document.querySelector(".book");
            book.classList.remove("honkit-cloak");
        }
    </script>
    <script src="gitbook/gitbook.js"></script>
    <script src="gitbook/theme.js"></script>
    
        
        <script src="gitbook/@dogatana/honkit-plugin-page-toc-button/plugin.js"></script>
        
    
        
        <script src="gitbook/@dogatana/honkit-plugin-back-to-top-button/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-copy-code-button/toggle.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-forkmegithub/plugin.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search-engine.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-search/search.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/lunr.min.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-lunr/search-lunr.js"></script>
        
    
        
        <script src="gitbook/gitbook-plugin-fontsettings/fontsettings.js"></script>
        
    

    </body>
</html>

